package com.cooper.util;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

/**
 * Util Class about object class.
 * @author Coraldane coraldane@gmail.com
 * @date Jul 7, 2011 18:16:46
 * @version 1.0
 */
@SuppressWarnings("rawtypes")
public class ClassUtils {
	
	private static Logger logger = LogManager.getLogger(ClassUtils.class);
	
	/** Suffix for array class names: "[]" */
	public static final String ARRAY_SUFFIX = "[]";
	
	private static final Map<Class, Class> primitiveWrapperTypeMap = new HashMap<Class, Class>(8);
	private static final Map<String, Class> primitiveTypeNameMap = new HashMap<String, Class>(16);
	private static final List<Class> dateTypeClassList = new ArrayList<Class>();
	private static final List<Class> numberTypeClassList = new ArrayList<Class>();
	private static Map<String, Class> classTypeMap = new HashMap<String, Class>();
	
	static {
		numberTypeClassList.add(int.class);
		numberTypeClassList.add(long.class);
		numberTypeClassList.add(double.class);
		numberTypeClassList.add(short.class);
		numberTypeClassList.add(float.class);
		numberTypeClassList.add(Number.class);
		numberTypeClassList.add(BigDecimal.class);
		numberTypeClassList.add(Integer.class);
		numberTypeClassList.add(Long.class);
		numberTypeClassList.add(Double.class);
		numberTypeClassList.add(Short.class);
		numberTypeClassList.add(Float.class);
		
		dateTypeClassList.add(Time.class);
		dateTypeClassList.add(java.sql.Date.class);
		dateTypeClassList.add(java.util.Date.class);
		dateTypeClassList.add(Timestamp.class);
		
		primitiveWrapperTypeMap.put(Boolean.class, boolean.class);
		primitiveWrapperTypeMap.put(Byte.class, byte.class);
		primitiveWrapperTypeMap.put(Character.class, char.class);
		primitiveWrapperTypeMap.put(Double.class, double.class);
		primitiveWrapperTypeMap.put(Float.class, float.class);
		primitiveWrapperTypeMap.put(Integer.class, int.class);
		primitiveWrapperTypeMap.put(Long.class, long.class);
		primitiveWrapperTypeMap.put(Short.class, short.class);
		primitiveWrapperTypeMap.put(Number.class, Number.class);
		primitiveWrapperTypeMap.put(BigDecimal.class, BigDecimal.class);

		Set<Class> primitiveTypeNames = new HashSet<Class>(16);
		primitiveTypeNames.addAll(primitiveWrapperTypeMap.values());
		primitiveTypeNames.addAll(Arrays.asList(new Class[] { boolean[].class,
				byte[].class, char[].class, double[].class, float[].class,
				int[].class, long[].class, short[].class }));
		for (Iterator it = primitiveTypeNames.iterator(); it.hasNext();) {
			Class primitiveClass = (Class) it.next();
			primitiveTypeNameMap.put(primitiveClass.getName(), primitiveClass);
		}
		
		/**
		 * initialize class type map
		 */
		Class clazz = com.cooper.core.Type.class;
		Field[] typeFields = clazz.getFields();
		for(Field field: typeFields){
			String fieldName = field.getName();
			try {
				Class fieldValue = (Class)field.get(new Object());
				classTypeMap.put(fieldName.substring(5), fieldValue);
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 判断是否继承自接口
	 * @param clazz
	 * @return
	 * @date Oct 19, 2011 4:01:45 PM
	 * @author coraldane
	 */
	public static boolean instanceofInterface(Class clazz){
		Class[] interfaces = clazz.getInterfaces();
		return 0 != interfaces.length;
	}
	
	/**
	 * decide if the Class is a primitive's box class
	 * 
	 * @param clazz
	 * @return
	 */
	public static boolean isPrimitiveWrapperType(Class clazz) {
		return primitiveWrapperTypeMap.containsKey(clazz);
	}

	/**
	 * Get the mapping from box class to primitive class eg. Boolean.class ->
	 * boolean.class
	 * 
	 * @param clazz
	 * @return
	 */
	public static Class getPrimitiveType(Class clazz) {
		return primitiveWrapperTypeMap.get(clazz);
	}

	/**
	 * @param requiredType
	 * @throws ClassNotFoundException
	 */
	public static Class parseClass(String requiredType) {
		Class clazz = String.class;
		if (null == requiredType || "".equals(requiredType)) {
			return clazz;
		}
		if (classTypeMap.containsKey(requiredType.toUpperCase())) {
			return classTypeMap.get(requiredType.toUpperCase());
		}
		return clazz;
	}

	/**
	 * 
	 * @param convertedValue source value need to convert
	 * @param requiredType dest value type
	 * @param format formatter,only used in Date format
	 * @return requiredType value
	 * @throws CoralException 
	 */
	public static Object parseDate(String convertedValue, Class requiredType, String format) {
		Object obj = new Date(0);
		if(null == convertedValue){
			return obj;
		}
//		Milliseconds of 2400-01-01
		long MAX_MILLSECONDS = 13569436800000L;
		if(!isDate(requiredType)){
			return obj;
		}
		SimpleDateFormat sdf = null;
		if ((format != null) && (!("".equals(format.trim())))){
			sdf = new SimpleDateFormat(format);
		}
		Date date = null;
		try {
			date = sdf.parse(convertedValue);
		} catch (ParseException e) {
			date = new Date(0);
		}
		if(date.getTime() > MAX_MILLSECONDS){
			date.setTime(MAX_MILLSECONDS);
		}
		if (requiredType == Timestamp.class) {
			obj = new Timestamp(date.getTime());
		} else if (requiredType == Time.class) {
			obj = new Time(date.getTime());
		} else if(java.util.Date.class == requiredType){
			obj = date;
		} else if(java.sql.Date.class == requiredType){
			obj = new java.sql.Date(date.getTime());
		}
		return obj;
	}
	
	/**
	 * This mothod Convert Object
	 * If requiredType is Primitive or Wrapper, then convert it into requiredType
	 * else if requiredType is String, then call <code>Object.toString()</code>
	 * otherwise return the object at all.
	 * Addtional, this method can't convert Date Object, please use <code>parseDate(Object, Class, String)</code>
	 * @param value
	 * @param requiredType
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> T parseObject(Object value, Class<T> requiredType){
		if(null == value){return null;}
		Object obj = null;
		if(isAssignableValue(requiredType, value)){
			obj = value;
		}else if(String.class == requiredType){
			obj = value.toString();
		} else if(isPrimitiveOrWrapper(requiredType)){
			if(value instanceof String || isPrimitiveOrWrapper(value.getClass())){
				if(isNumeric(requiredType) && "".equals(value.toString().trim())){
					value = "0";
				}
				obj = parseFromString(value.toString().trim(), requiredType);
			}
		}
		return (T)obj;
	}
	
	/**
	 * Parse Object from String to Primitive type or Wrapper type
	 * @param value
	 * @param requiredType
	 * @return
	 */
	public static Object parseFromString(String value, Class requiredType) {
		logger.trace("Start to convert " + value + " to type[" + requiredType + "]");
		Object obj = null;
		if (int.class == requiredType || Integer.class == requiredType) {
			double doubleValue = Double.parseDouble(value);
			obj = new Double(doubleValue).intValue();
		} else if (double.class == requiredType || Double.class == requiredType) {
			obj = Double.parseDouble(value);
		} else if (float.class == requiredType || Float.class == requiredType) {
			obj = Float.parseFloat(value);
		} else if (short.class == requiredType || Short.class == requiredType) {
			obj = Short.parseShort(value);
		} else if (long.class == requiredType || Long.class == requiredType) {
			obj = Long.parseLong(value);
		} else if (boolean.class == requiredType || Boolean.class == requiredType) {
			obj = Boolean.parseBoolean(value);
		} else if (byte.class == requiredType || Byte.class == requiredType) {
			obj = Byte.parseByte(value);
		} else if (char.class == requiredType || Character.class == requiredType) {
			obj = value.charAt(0);
		} else if (requiredType == BigDecimal.class) {
			obj = new BigDecimal(value);
		} else if (requiredType == Number.class) {
			obj = new BigDecimal(value);
		} else if (isDate(requiredType)) {
			obj = new Date(0);
		}
		return obj;
	}
	
	/**
	 * 
	 * @param clazz
	 * @return
	 */
	public static boolean isDate(Class clazz){
		return dateTypeClassList.contains(clazz);
	}
	
	public static boolean isNumeric(Class clazz){
		return numberTypeClassList.contains(clazz);
	}
	
	/**
	 * Get Setter method name by Class name
	 * @param clazz
	 * @return
	 */
	public static String getSetterMethodName(Class clazz){
		String methodName = "set";
		if (ClassUtils.isPrimitiveWrapperType(clazz)) {
			clazz = ClassUtils.getPrimitiveType(clazz);
		}
		String className = clazz.getName();
		if (clazz.isPrimitive()) {
			methodName += Character.toUpperCase(className.charAt(0)) + className.substring(1);
		} else {
			methodName += className.substring(className.lastIndexOf(".") + 1);
		}
		return methodName;
	}
	
	/**
	 * Get Getter method name by Class name
	 * @param clazz
	 * @return
	 */
	public static String getGetterMethodName(Class clazz){
		String methodName = "get";
		if (ClassUtils.isPrimitiveWrapperType(clazz)) {
			clazz = ClassUtils.getPrimitiveType(clazz);
		}
		String className = clazz.getName();
		if (clazz.isPrimitive()) {
			methodName += Character.toUpperCase(className.charAt(0)) + className.substring(1);
		} else {
			methodName += className.substring(className.lastIndexOf(".") + 1);
		}
		return methodName;
	}
	
	/**
	 * 
	 * @param lhsType
	 * @param rhsType
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static boolean isAssignable(Class lhsType, Class rhsType) {
		return lhsType.isAssignableFrom(rhsType)
				|| lhsType == primitiveWrapperTypeMap.get(rhsType);
	}

	public static boolean isAssignableValue(Class type, Object value) {
		return value == null ? !type.isPrimitive() : isAssignable(type, value.getClass());
	}

	public static boolean isPrimitiveOrWrapper(Class clazz) {
		return clazz.isPrimitive()
				|| primitiveWrapperTypeMap.containsKey(clazz);
	}

	public static String getQualifiedName(Class clazz) {
		if (clazz.isArray())
			return getQualifiedNameForArray(clazz);
		else
			return clazz.getName();
	}

	private static String getQualifiedNameForArray(Class clazz) {
		StringBuffer buffer = new StringBuffer();
		for (; clazz.isArray(); buffer.append("[]"))
			clazz = clazz.getComponentType();

		buffer.insert(0, clazz.getName());
		return buffer.toString();
	}

	public static boolean isWrapClass(Class clz) {
		try {
			return ((Class) clz.getField("TYPE").get(null)).isPrimitive();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		}
		return false;
	}
	
	public static Object getDefaultValue(Class clazz){
		if(String.class == clazz){
			return " ";
		} else if(isNumeric(clazz)){
			return 0;
		} else if(boolean.class == clazz || Boolean.class == clazz){
			return false;
		} else if(isDate(clazz)){
			if(java.sql.Timestamp.class == clazz){
				return new Timestamp(0);
			} else if(java.sql.Time.class == clazz){
				return new Time(0);
			} else if(java.sql.Date.class == clazz){
				return new java.sql.Date(0);
			} else if(java.util.Date.class == clazz){
				return new java.util.Date(0);
			}
		} else if(char.class == clazz || Character.class == clazz){
			return 'A';
		} else if(byte.class == clazz || Byte.class == clazz){
			return 1;
		}
		return null;
	}

	public static Map<String, Class> getClassTypeMap() {
		return classTypeMap;
	}
	
	public static ClassLoader getDefaultClassLoader() {
		ClassLoader cl = null;
		try {
			cl = Thread.currentThread().getContextClassLoader();
		} catch (Throwable ex) {
			logger.debug("Cannot access thread context ClassLoader - falling back to system class loader", ex);
		}
		if (cl == null) {
			// No thread context class loader -> use class loader of this class.
			cl = ClassUtils.class.getClassLoader();
		}
		return cl;
	}
	
}